package gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.DecimalFormat;

import javax.swing.JComponent;

import parser.LexError;
import parser.ParseError;
import parser.Parser;
import visitor.CalculateDoubles;
import exprtree.Expr;

/**
 * 
 * @author Brad
 */
public class GraphComponent extends JComponent {

	private static final long serialVersionUID = 1L;
	Expr expr;
	double a = -10;       //World Coordinates
	double b = 10;
	double xMin = -10;
	double xMax = 10;
	double yMin = -10;
	double yMax = 10;
	int width = 400;   //Screen Coordinates
	int height = 400;
	double tanX = 2.0;
	boolean showTan = false;
	
	int left = -(this.width/2);
	int right = (this.width/2);
	int top = (this.height/2);
	int bottom = -(this.height/2);
	CalculateDoubles evaluator = new CalculateDoubles();

	/**
	 * Sets the expression for the graphing component to draw.
	 * @param exprIn
	 */
	public void setExpression(Expr exprIn) {
		expr = exprIn;
	}
	
	/**
	 * Sets the expression for the graphing component to draw.
	 * @param exprString  -  Parses it here
	 * @throws ParseError 
	 * @throws LexError 
	 */
	public void setExpression(String exprString) throws LexError, ParseError {
		Parser parser = new Parser();
		expr = parser.parse(exprString);
	}
	
	/**
	 * Sets the a and b used to calculate the area.
	 * @param a
	 * @param b
	 */
	public void setAB(double a, double b){
		this.a = a;
		this.b = b;
	}
	

	/**
	 * Main painting method of this graphing component.
	 */
	public synchronized void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		
		
		g2.translate((this.width/2), (this.height/2));   //Move origin to center
		g2.scale(1.0, -1.0);  //Flip on the x-axis;

		drawAxes(g2);  //TODO:  Need to adjust based on a and b.
		g2.setColor(Color.BLUE);
		if(expr != null){
			
			double interval = (double)(Math.abs(xMin) + Math.abs(xMax)) / (this.width);
			double xMultiple = (Math.abs(xMin) + Math.abs(xMax));
			double yMultiple = (Math.abs(yMin) + Math.abs(yMax));
			double x1 = xMin;
			double x2 = xMin;
			evaluator.setX(x1);
			double y1 = 0;
			double y2 = (Double) evaluator.visit(expr);
			
			for (int x = left ; x < right; x++) {
				x1 = x2;
				x2 = x2+interval;
				y1 = y2;
				evaluator.setX(x2);
				
				y2 = (Double) evaluator.visit(expr);
				
				g2.setColor(Color.BLUE);
				g2.drawLine((int)(x1 * xMultiple), (int)(y1 * yMultiple), (int)(x1 * xMultiple), (int)(y2*yMultiple));
				
				//Draw area
				if(x1 > a && x1 < b){
					g2.setColor(new Color(0.0f, 1.0f, 1.0f, 0.2f));
					g2.drawLine((int)(x1 * xMultiple), 0, (int)(x1 * xMultiple), (int)(y1*yMultiple));
				}
			}
			
			if(showTan){
				drawTan(g2);
			}
			else{
				g2.scale(1.0, -1.0);  //flip again...
			}
			g2.setColor(new Color(0.0f, 0.25f, 1.0f, 0.75f));
			DecimalFormat twoDForm = new DecimalFormat("#.##");
			g2.drawString("Area: " + Double.valueOf(twoDForm.format(getArea())), -190, 150);
		}
	}
	
	private synchronized void drawTan(Graphics2D g){
		g.setColor(Color.RED);
		double interval = (double)(Math.abs(xMin) + Math.abs(xMax)) / (this.width);
		double yMultiple = (Math.abs(yMin) + Math.abs(yMax));
		evaluator.setX((double)tanX-interval);
		double rise1 = (Double) evaluator.visit(expr);
		evaluator.setX((double)tanX+interval);
		double rise2 = (Double) evaluator.visit(expr);
			
		double rise = rise2 - rise1;
		double run = interval*2;
		double slope = (rise/run);
		
		// Point Slope
		// y - y1 = m(x - x1)
		double sampleX = tanX;
		evaluator.setX(sampleX);
		double sampleY = (Double) evaluator.visit(expr);
		
		// b = -mx1 + y1  y-intercept
		double intercept = -((rise/run) * sampleX) + sampleY;	
		
		g.drawLine(left, (int)((rise/run)*left + intercept * yMultiple), right, (int)((rise/run)*right + intercept * yMultiple));
		
		g.scale(1.0, -1.0);  //flip again...
		DecimalFormat twoDForm = new DecimalFormat("#.##");
		g.drawString("Tangent Line: y = " + twoDForm.format(slope) + "x + " + twoDForm.format(intercept), -190, 190);
		g.drawString("x = " + tanX + ", y = " + twoDForm.format(sampleY), -190, 170);
		
	}
	
	/**
	 * Returns the area of the given expression between points a and b.
	 * @return area
	 */
	public synchronized double getArea(){
		double area = 0;
		for (double i = a; i < b; i += .001) {
			evaluator.setX(i);
			area += (((Double)(evaluator.visit(expr))) * .001);
		}
		return area;
	}
	
	private void drawAxes(Graphics2D g){
		double xMultiple = (Math.abs(xMin) + Math.abs(xMax));
		double yMultiple = (Math.abs(yMin) + Math.abs(yMax));
		
		g.setColor(Color.black);		
		g.drawLine(left, 0, right, 0);     //X-axis
		//int x = (((Math.abs(a)+Math.abs(b))/Math.abs(a))*(this.width/2));
		g.drawLine(0, top, 0, bottom); //Y-axis
		
		for (int i = left; i < right; i += xMultiple) {
			g.drawLine(i, -2, i, 2);
		}
		for (int i = bottom; i < top; i += yMultiple){
			g.drawLine(-2, i, 2, i);
		}
	}
	
	/**
	 * Sets whether or not to show a the tangent line
	 * @param set
	 */
	public void setShowTan(boolean set){
		showTan = set;
	}
	public boolean isShowTan(){
		return showTan;
	}
	
	/**
	 * Sets the x position of the tangent line of a given expression
	 * @param tanX
	 */
	public void setTanX(double tanX){
		this.tanX = tanX;
	}
	public double getTanX(){
		return tanX;
	}

	public Dimension getPreferredSize() {
		return new Dimension(400, 400);
	}

	public Dimension getMinimumSize() {
		return getPreferredSize();
	}

}